/*
 * Created on 28/09/2006
 */
package org.mushroomdb.parser.simpleparser;

import java.sql.Types;
import java.text.DateFormat;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.regex.Pattern;

import org.mushroomdb.catalog.Catalog;
import org.mushroomdb.catalog.Column;
import org.mushroomdb.catalog.Table;
import org.mushroomdb.parser.Parser;
import org.mushroomdb.parser.ParserException;
import org.mushroomdb.query.Query;
import org.mushroomdb.query.impl.InsertionQuery;
import org.mushroomdb.query.impl.LiteralsSelectionQuery;
import org.mushroomdb.query.impl.SelectionQuery;

/**
 * 
 * INSERT INTO {table-name} ({field1}[,{field2}[,...]])
 * VALUES ({value1}[,{value2}[,...]]);
 * 
 * INSERT INTO {table-name} ({field1}[,{field2}[,...]])
 * {selection-query};
 * 
 * @author mchiodi
 */
public class InsertionQueryParser implements Parser {
	
	private Table table;
	private List fields;
	private List values;
	private SelectionQuery selectionQuery;
	
	/**
	 * Default constructor
	 */
	public InsertionQueryParser() {
	}
	
	/**
	 * @see org.mushroomdb.parser.Parser#parseQuery(java.lang.String)
	 */
	public Query parseQuery(String query) throws ParserException {
		
		this.fields = new LinkedList();
		this.selectionQuery = null;
		
		StringTokenizer tokenizer = new StringTokenizer(query," (),",true);
		String token = null;
		boolean badToken = false;
		
		/* INSERT */
		if (tokenizer.hasMoreTokens()) {
			token = tokenizer.nextToken();
			if (!"INSERT".equals(token.toUpperCase())) {
				badToken = true;
			}
			/* Consume delimiter */
			if (tokenizer.hasMoreTokens()) {
				tokenizer.nextToken();
			} else {
				badToken = true;
			}
		}
		else {
			badToken = true;
		}
		if (badToken) {
			throw new ParserException("INSERT keyword expected");
		}
		
		/* INTO */
		if (tokenizer.hasMoreTokens()) {
			
			/* Consume remaining delimiters */
			token = tokenizer.nextToken();
			while (this.isDelimiter(token)) {
				token = tokenizer.nextToken();
			}
			/* Consume INTO */
			if (!"INTO".equals(token.toUpperCase())) {
				badToken = true;
			}
			/* Consume delimiter */
			if (tokenizer.hasMoreTokens()) {
				tokenizer.nextToken();
			} else {
				badToken = true;
			}
		}
		else {
			badToken = true;
		}
		if (badToken) {
			throw new ParserException("INTO keyword expected");
		}
		
		/* {table-name} */
		if (tokenizer.hasMoreTokens()) {
			
			/* Consume remaining delimiters */
			token = tokenizer.nextToken();
			while (this.isDelimiter(token)) {
				token = tokenizer.nextToken();
			}
			/* Consume {table-name} */
			Catalog catalog = Catalog.getInstance();
			this.table = catalog.getTableByName(token);
			if (this.table == null) {
				throw new ParserException("Table " + token + " does not exist");
			}
			
			/* Consume delimiter */
			if (tokenizer.hasMoreTokens()) {
				tokenizer.nextToken();
			} else {
				badToken = true;
			}
		}
		else {
			badToken = true;
		}
		if (badToken) {
			throw new ParserException("INTO keyword expected");
		}
		
		/* Field names start = '(' */
		if (tokenizer.hasMoreTokens()) {
			
			/* Consume remaining delimiters */
			token = tokenizer.nextToken();
			while (this.isDelimiter(token) && !"(".equals(token)) {
				token = tokenizer.nextToken();
			}
			/* Consume field names start */
			if (!"(".equals(token.toUpperCase())) {
				badToken = true;
			}
		}
		else {
			badToken = true;
		}
		if (badToken) {
			throw new ParserException("'(' character expected");
		}
		
		/* Field names */
		if (tokenizer.hasMoreTokens()) {
			
			boolean endReached = false;
			while (!endReached) {
				
				/* Consume remaining delimiters */
				token = tokenizer.nextToken();
				while (this.isDelimiter(token) && !")".equals(token)) {
					token = tokenizer.nextToken();
				}
				if (")".equals(token)) {
					endReached = true;
				}
				
				/* Consume field name */
				if (!endReached) {
					Column column = this.table.getColumn(token);
					if (column != null) {
						this.fields.add(column);
					} else {
						throw new ParserException("Column " + token + " does not exist for table " + this.table.getName());
					}
				}
			}
		}
		else {
			badToken = true;
		}
		if (badToken) {
			throw new ParserException("Fields expected");
		}
		
		/* VALUES or SELECT */
		if (tokenizer.hasMoreTokens()) {
			
			/* Consume remaining delimiters */
			token = tokenizer.nextToken();
			while (this.isDelimiter(token)) {
				token = tokenizer.nextToken();
			}
			/* Consume SELECT */
			if ("SELECT".equals(token.toUpperCase())) {
				// TODO
			} else if ("VALUES".equals(token.toUpperCase())) {
				this.values = new LinkedList();
			} else {
				badToken = true;
			}
			/* Consume delimiter */
			if (tokenizer.hasMoreTokens()) {
				tokenizer.nextToken();
			} else {
				badToken = true;
			}
		}
		else {
			badToken = true;
		}
		if (badToken) {
			throw new ParserException("VALUES or SELECT keyword expected");
		}
		
		/* Values start = '(' */
		if (tokenizer.hasMoreTokens()) {
			
			/* Consume remaining delimiters */
			token = tokenizer.nextToken();
			while (this.isDelimiter(token) && !"(".equals(token)) {
				token = tokenizer.nextToken();
			}
			/* Consume values start */
			if (!"(".equals(token.toUpperCase())) {
				badToken = true;
			}
		}
		else {
			badToken = true;
		}
		if (badToken) {
			throw new ParserException("'(' character expected");
		}
		
		/* Values list */
		if (tokenizer.hasMoreTokens()) {
			
			boolean endReached = false;
			int columnIndex = 0;
			while (!endReached) {
				
				/* Consume remaining delimiters */
				token = tokenizer.nextToken();
				while (this.isDelimiter(token) && !")".equals(token)) {
					token = tokenizer.nextToken();
				}
				if (")".equals(token)) {
					endReached = true;
				}
				
				String stringValue = token;
				if (stringValue.startsWith("'")) {
					while (!Pattern.matches("'.*'",stringValue)) {
						if (tokenizer.hasMoreTokens()) {
							stringValue = stringValue + tokenizer.nextToken();
						} else {
							throw new ParserException("String values must end with '''");
						}
					}
				}
				
				/* Consume value */
				if (!endReached) {
					Column column = (Column) this.fields.get(columnIndex);
					this.values.add(this.getValue(column,stringValue));
					columnIndex++;
				}
			}
		}
		else {
			badToken = true;
		}
		if (badToken) {
			throw new ParserException("Fields expected");
		}
		
		/* Build the query */
		if (this.selectionQuery == null) {
			this.selectionQuery = new LiteralsSelectionQuery(this.fields);
			Iterator iterator = this.values.listIterator();
			while (iterator.hasNext()) {
				this.selectionQuery.addProjection(iterator.next());
			}
		}
		return new InsertionQuery(this.selectionQuery,this.table);
	}
	
	/**
	 * @param column
	 * @param valueString
	 * @return
	 */
	private Object getValue(Column column, String valueString) throws ParserException {
		
		Object value = valueString;
		
		if (column.getType() == Types.BIGINT) {
			value = new Long(valueString);
		} else if (column.getType() == org.mushroomdb.catalog.Types.IDENTITY) {
			value = new Long(valueString);
		} else if (column.getType() == Types.INTEGER) {
			value = new Integer(valueString);
		} else if (column.getType() == Types.FLOAT) {
			value = new Float(valueString);
		} else if (column.getType() == Types.BOOLEAN) {
			value = new Boolean(valueString);
		} else if (column.getType() == Types.SMALLINT) {
			value = new Short(valueString);
		} else if (column.getType() == Types.TINYINT) {
			value = new Byte(valueString);
		} else if (column.getType() == Types.DATE) {
			DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT);
			try {
				valueString = valueString.replaceAll("-","/"); //$NON-NLS-1$ //$NON-NLS-2$
				value = dateFormat.parse(valueString);
			} catch (java.text.ParseException e) {
				throw new ParserException(e.getMessage());
			}
		} else if (column.getType() == Types.CHAR) {
			value = valueString.replaceAll("'",""); //$NON-NLS-1$ //$NON-NLS-2$
		}
		
		return value;
	}
	
	/**
	 * @param delimiter
	 * @return
	 */
	private boolean isDelimiter(String delimiter) {
		return
			" ".equals(delimiter) ||
			"(".equals(delimiter) ||
			")".equals(delimiter) ||
			",".equals(delimiter);
	}
	
	/**
	 * @see org.mushroomdb.parser.Parser#accepts(java.lang.String)
	 */
	public boolean accepts(String query) {
		return Pattern.matches("\\s*INSERT\\s+INTO\\s+.*",query.toUpperCase()); //$NON-NLS-1$
	}

}
